package mbolt

import (
	"encoding/json"
	"errors"
	"github.com/boltdb/bolt"
	"time"
)

var Default *Bolt
var GetDefault = func() *Bolt {
	if Default != nil {
		return Default
	}
	panic("bolt not initialize")
}

type Bolt struct {
	Bolt *bolt.DB
}

func (ce *Bolt) R() (*BoltTran, error) {
	if tx, err := ce.Bolt.Begin(false); err == nil {
		return &BoltTran{Bolt: ce.Bolt, Tx: tx, ReadOnly: true}, nil
	} else {
		return nil, err
	}
}
func (ce *Bolt) RW() (*BoltTran, error) {
	if tx, err := ce.Bolt.Begin(true); err == nil {
		return &BoltTran{Bolt: ce.Bolt, Tx: tx, ReadOnly: false}, nil
	} else {
		return nil, err
	}
}

type BoltTran struct {
	Bolt     *bolt.DB
	Tx       *bolt.Tx
	ReadOnly bool
}

func (ce *BoltTran) Commit() error {
	return ce.Tx.Commit()
}
func (ce *BoltTran) Rollback() error {
	return ce.Tx.Rollback()
}

func (ce *BoltTran) Bucket(bucketName string) *BoltTranBucket {
	bucket := ce.Tx.Bucket([]byte(bucketName))
	if bucket == nil {
		panic(errors.New("bucket is not found " + bucketName))
	}
	return &BoltTranBucket{
		Bolt:   ce.Bolt,
		Tx:     ce.Tx,
		Bucket: bucket,
	}
}

type BoltTranBucket struct {
	Bolt   *bolt.DB
	Tx     *bolt.Tx
	Bucket *bolt.Bucket
}

func (ce *BoltTranBucket) GetBytes(key string) []byte {
	return ce.Bucket.Get([]byte(key))
}

func (ce *BoltTranBucket) Get(key string) string {
	if b := ce.Bucket.Get([]byte(key)); b == nil {
		return ""
	} else {
		return string(b)
	}
}
func (ce *BoltTranBucket) SetBytes(key string, value []byte) error {
	return ce.Bucket.Put([]byte(key), value)
}
func (ce *BoltTranBucket) Set(key string, value string) error {
	return ce.SetBytes(key, []byte(value))
}

func (ce *BoltTranBucket) Delete(key string) error {
	return ce.Delete(key)
}

func (ce *BoltTranBucket) GetJson(key string, dest any) (any, error) {
	b := ce.GetBytes(key)
	if b == nil {
		return nil, nil
	}
	if err := json.Unmarshal(b, dest); err == nil {
		return dest, nil
	} else {
		return nil, err
	}
}

func (ce *BoltTranBucket) SetJson(key string, dest any) error {
	if b, err := json.Marshal(dest); err == nil {
		return ce.SetBytes(key, b)
	} else {
		return err
	}
}

func InitBolt(filepath string, bucketName ...string) (*Bolt, error) {
	if db, err := bolt.Open(filepath, 0600, &bolt.Options{
		Timeout:    1 * time.Second,
		NoGrowSync: false,
	}); err == nil {
		if tx, err := db.Begin(true); err == nil {
			defer tx.Rollback()
			for _, name := range bucketName {
				if _, err := tx.CreateBucketIfNotExists([]byte(name)); err != nil {
					return nil, err
				}
			}
			tx.Commit()
			return &Bolt{Bolt: db}, nil
		} else {
			return nil, err
		}

	} else {
		return nil, err
	}

}
func InitDefaultBolt(filepath string, bucketName ...string) error {
	if db, err := InitBolt(filepath, bucketName...); err == nil {
		Default = db
		GetDefault = getDefault
		return nil
	} else {
		return err
	}
}
